/* 
 * File:   surfacealigndomain.h
 * Author: boyan
 *
 * Created on April 1, 2009, 2:47 PM
 */

#ifndef _SURFACEALIGNDOMAIN_H
#define	_SURFACEALIGNDOMAIN_H

#include <cmath>

#include "AlignDomain.h"

#include <Vector3D.hpp>
#include <MsqError.hpp>

using Mesquite::Mesh;
using Mesquite::Vector3D;
using Mesquite::MsqError;

class SurfaceAlignDomain : public AlignDomain
{
public:

    SurfaceAlignDomain(Mesh &surf);

    virtual ~SurfaceAlignDomain() { };

    // inherited from AlignDomain
    virtual LevelsetCode levelset_code(Mesquite::Mesh::EntityHandle entity_handle,
        const Mesquite::Vector3D &pos) const;

    // inherited from MeshDomain
    virtual void snap_to(Mesh::EntityHandle entity_handle,
        Vector3D &coordinate) const;
    virtual void normal_at(Mesh::EntityHandle entity_handle,
        Vector3D &coordinate) const;
    virtual void normal_at(const Mesh::EntityHandle* handles,
        Vector3D coordinates[],
        unsigned count,
        MsqError& err) const;
    virtual void closest_point(Mesh::EntityHandle handle,
        const Vector3D& position,
        Vector3D& closest,
        Vector3D& normal,
        MsqError& err) const;

    virtual void domain_DoF(const Mesh::EntityHandle* handle_array,
        unsigned short* dof_array, size_t num_handles, MsqError& err) const
    {
        for(size_t i=0; i<num_handles; i++)
            dof_array[i]=2;
    }

    virtual void write_vtk(const char* filename) const;

private:

    class SurfNode : public Vector3D
    {
    protected:
        double mKappa; // curvature == 1/Radius;
        Vector3D mCenter;
        size_t mEdgesNum;
        signed int mNormalSign;

    public:

        SurfNode(double x, double y, double z) : Vector3D(x, y, z), mKappa(0.0),
        mCenter(0, 0, 0), mEdgesNum(0), mNormalSign(0) { }

        SurfNode(const Vector3D &vec) : Vector3D(vec), mKappa(0.0),
        mCenter(0, 0, 0), mEdgesNum(0), mNormalSign(0) { }

        SurfNode() : Vector3D(0, 0, 0), mKappa(0.0), mCenter(0, 0, 0),
        mEdgesNum(0), mNormalSign(0) { }

        SurfNode(const SurfNode& rhs) : Vector3D(rhs), mKappa(rhs.mKappa),
        mCenter(rhs.mCenter), mEdgesNum(rhs.mEdgesNum),
        mNormalSign(rhs.mNormalSign) { }

        SurfNode&operator=(const Vector3D& rhs);
        SurfNode&operator=(const SurfNode& rhs);

        Vector3D GetSurfNormal() const;
        Vector3D GetSurfNormal(const Vector3D& pt) const;
        Vector3D GetBallNormal() const;
        Vector3D GetBallNormal(const Vector3D& pt) const;
        Vector3D GetProjection(const Vector3D& pt) const;


        bool IsNormalSignSet() const;
        signed int GetNormalSign() const;
        void SetNormalSign(signed int sign);

        double &BallKappa();
        Vector3D &BallCentre();
        size_t &AttachedEdges();
        size_t &AttachedNodes();


    };

    friend bool __radius_centre_of_ball(SurfaceAlignDomain::SurfNode &node,
        std::vector<Vector3D> &pts);

    struct SurfFace
    {
        size_t nodes[3];
        size_t edges[3];
        SurfFace(const size_t*nds);
        void set_edge(size_t edge, const size_t*ends);
        bool has_node(size_t nd) const;
        Vector3D GetSurfNormal(const std::vector<SurfNode> &nodes) const;
    };

    struct SurfEdge
    {
        // nodes[0,1] define edge;
        // nodes[2,3] are third nodes of faces[0,1] (inverse connectivity);
        size_t nodes[4];
        size_t faces[2];
        SurfEdge(const size_t*nds);
        void set_face(size_t idx, size_t face, const size_t*fnds);
        bool has_node(size_t nd) const;
    };

    std::vector<SurfNode> nodes;
    std::vector<SurfFace> faces;
    std::vector<SurfEdge> edges;

    void init(Mesh &surf); // takes nodes and faces from grid
    void build_edges(); // builds edges and computes balls
    void compute_balls();
    void __compute_ball(size_t node_index);
};


////  The SurfNode inlines  /////////////////////////////////////////////////

inline double& SurfaceAlignDomain::SurfNode::BallKappa()
{
    return mKappa;
}

inline Vector3D& SurfaceAlignDomain::SurfNode::BallCentre()
{
    return mCenter;
}

inline size_t& SurfaceAlignDomain::SurfNode::AttachedEdges()
{
    return mEdgesNum;
}

inline size_t& SurfaceAlignDomain::SurfNode::AttachedNodes()
{
    return mEdgesNum;
}

inline bool SurfaceAlignDomain::SurfNode::IsNormalSignSet() const
{
    return mNormalSign!=0;
}

inline signed int SurfaceAlignDomain::SurfNode::GetNormalSign() const
{
    return mNormalSign;
}

inline void SurfaceAlignDomain::SurfNode::SetNormalSign(signed int sign)
{
    mNormalSign=1;
    if(sign<0)
        mNormalSign= -1;
}

////  The SurfFace inlines  /////////////////////////////////////////////////

inline SurfaceAlignDomain::SurfFace::SurfFace(const size_t*nds)
{
    nodes[0]=nds[0];
    nodes[1]=nds[1];
    nodes[2]=nds[2];
}

inline bool SurfaceAlignDomain::SurfFace::has_node(size_t nd) const
{
    return(nd==nodes[0])||(nd==nodes[1])||(nd==nodes[2]);
}

inline void SurfaceAlignDomain::SurfFace::
set_edge(size_t edge, const size_t *ends)
{
    register size_t third=(nodes[0]+nodes[1]+nodes[2])-(ends[0]+ends[1]);
    if(nodes[0]==third)
        edges[0]=edge;
    else if(nodes[1]==third)
        edges[1]=edge;
    else
        edges[2]=edge;
}

//// The SurfEdge inlines  //////////////////////////////////////////////////

inline SurfaceAlignDomain::SurfEdge::SurfEdge(const size_t*nds)
{
    nodes[0]=nds[0];
    nodes[1]=nds[1];
}

inline void SurfaceAlignDomain::SurfEdge::
set_face(size_t idx, size_t face, const size_t*fnds)
{
    assert(idx<2);
    faces[idx]=face;
    nodes[idx+2]=(fnds[0]+fnds[1]+fnds[2])-(nodes[0]+nodes[1]);
}

inline bool SurfaceAlignDomain::SurfEdge::has_node(size_t nd) const
{
    return(nd==nodes[0])||(nd==nodes[1]);
}


#endif	/* _SURFACEALIGNDOMAIN_H */

